#include <errno.h>

#include "Udp.h"

Udp::Udp(std::string remote_address, int remote_port)
{
#ifdef WIN32
	struct WSAData wsadata;
#endif
    is_bound_ = false;
	int startup = STARTUP(UINT_MAX, &wsadata);

	if(startup != 0)
	{
		printf("Failed to execute startup\n");
        return;
	}

	struct protoent* proto = getprotobyname("UDP");

	if(proto == NULL)
	{
		printf("Couldn't get protocol\n");
	}
    
    socket_ = socket(AF_INET, SOCK_DGRAM, proto->p_proto);
    
    if(socket_ < 0)
    {
        printf("Failed to get a socket\n");
    }

	remote_.sin_family = AF_INET;
    remote_.sin_port = htons(remote_port);
    remote_.sin_addr.s_addr = inet_addr(remote_address.c_str());
    
    printf("Setting remote address to: ");
    PrintAddress(&remote_);
    printf("\n");
}

Udp::~Udp(void)
{
	if(socket_ != 0)
		CLOSE_SOCKET(socket_);
}

void Udp::Send(abyte* data, int length)
{
    size_t sent = sendto(socket_, data, length, 0, (sockaddr*)&remote_, sizeof(remote_));
	printf("Sent %ld bytes on UDP to: ", sent);
	PrintAddress(&remote_);
    printf("\n");
}

abyte* Udp::Receive(abyte* buffer, int length)
{
    if(!is_bound_)
    {
        is_bound_ = Bind();
        if(!is_bound_)
            return NULL;
    }
    
    struct sockaddr_in sender;
    asocklen_t sender_size = sizeof(sender);
    int received = (int)recvfrom(socket_, buffer, length, 0, (sockaddr*)&sender, &sender_size);
    
    // in Windows SOCKET_ERROR is -1, and on Unix success is non-negative, so this check should work for both.
	if(received < 0)
	{
		printf("Failed to receive data\n");
	}

    //printf("Udp received %d bytes from ", received);
	PrintAddress(&sender);
	printf("\n");
    return buffer;
}

bool Udp::Bind()
{
    struct sockaddr_in local;
    local.sin_family = AF_INET;
    local.sin_port = remote_.sin_port;
    local.sin_addr.s_addr = INADDR_ANY;
    
    printf("Binding to ");
    PrintAddress(&local);
    printf("\n");
    PrintAddress(&remote_);
    printf("\n");
    
    if(bind(socket_, (sockaddr*)&local, sizeof(local)) != 0)
    {
        printf("Failed to bind listening socket because: %s\n", strerror(errno));
        return false;
    }
    
    return true;
}

void Udp::PrintAddress(const sockaddr_in* address)
{
	char sender_address[16] = {0};
    char* sender_address_pointer = sender_address;
    sender_address_pointer = inet_ntoa(address->sin_addr);
	printf("%s:%d", sender_address_pointer, address->sin_port);
}